package com.cjx.stars;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ssh.Sftp;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.cjx.common.model.entity.InterfaceInfo;
import com.cjx.common.model.entity.User;
import com.cjx.common.service.inner.InterfaceInfoServiceInner;
import com.cjx.common.service.inner.UserServiceInner;
import com.cjx.starsapisdk.exception.BusinessException;
import com.cjx.starsapisdk.exception.ErrorCode;
import com.cjx.starsapisdk.utils.SignUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.redis.connection.ReactiveHashCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author cjx
 */
@Component
@Slf4j
public class GatewayGlobalFilter implements GlobalFilter, Ordered {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @DubboReference
    private InterfaceInfoServiceInner interfaceInfoServiceInner;
    @DubboReference
    private UserServiceInner userServiceInner;

    public static final String GATEWAY_NONCE = "gateway:nonce:" ;
    public static final int TIMEOUT_PERIOD = 60 * 5 ;

    /**
     * 请求白名单
     */
    private final static List<String> WHITE_HOST_LIST = Arrays.asList("127.0.0.1", "101.43.61.87");
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        //1. 请求日志
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getPath().value();
        String method = Objects.requireNonNull(request.getMethod()).toString();
        log.info("请求唯一id为：{}",request.getId());
        log.info("请求路径为：{}",uri);
        log.info("请求参数为：{}",request.getQueryParams());
        log.info("请求方法为：{}",method);
        log.info("请求来源路径为：{}",request.getRemoteAddress());
        //2. 黑白名单校验
        String hostString = Objects.requireNonNull(request.getRemoteAddress()).getHostString();
        if (!WHITE_HOST_LIST.contains(hostString)){
            return exceptionReturned(response,HttpStatus.FORBIDDEN);
        }
        //3. 用户权限校验
        HttpHeaders headers = request.getHeaders();
        String nonce = headers.getFirst("nonce");
        String timestamp = headers.getFirst("timestamp");
        String accessKey = headers.getFirst("access_key");
        String sign = headers.getFirst("sign");
        HashMap<String,String> singleValueMap = new HashMap<>();
        singleValueMap.put("nonce",nonce);
        singleValueMap.put("timestamp",timestamp);
        singleValueMap.put("access_key",accessKey);
        if (StrUtil.hasBlank(nonce,timestamp,accessKey,sign)){
            return exceptionReturned(response,HttpStatus.FORBIDDEN);
        }
        //3.1 校验随机数
        String key = GATEWAY_NONCE + nonce;
        Boolean hasKey = stringRedisTemplate.hasKey(key);
        try {
        if (BooleanUtil.isTrue(hasKey)){
            return exceptionReturned(response,HttpStatus.FORBIDDEN);
        }else {
            stringRedisTemplate.opsForSet().add(key,nonce);
        }
        }catch (Exception e){
            return exceptionReturned(response,HttpStatus.INTERNAL_SERVER_ERROR);
        }
        //3.2 校验时间戳是否超时
        long nowSecond = System.currentTimeMillis() / 1000;
        if (nowSecond -  Long.parseLong(timestamp)  >= TIMEOUT_PERIOD){
            return exceptionReturned(response,HttpStatus.FORBIDDEN);
        }
        //3.3 获取用户的SK（RPC）
        User user = userServiceInner.getUserByAssertKey(accessKey);
        System.out.println("User信息： "+user);
        if (user == null){
            return exceptionReturned(response,HttpStatus.FORBIDDEN);
        }
        //3.4 校验sign是否合法
        String newSign = SignUtil.getSign(singleValueMap, user.getSecretKey());
        if (!sign.equals(newSign)){
            return exceptionReturned(response,HttpStatus.FORBIDDEN);
        }
        //4. 获取接口信息
        InterfaceInfo interfaceInfo = interfaceInfoServiceInner.getInterfaceInfoByMethodAndUri(method, uri);
        System.out.println("interfaceInfo = " + interfaceInfo);
//        Mono<Void> filter = chain.filter(exchange);
        //5. 响应日志
        return handleResponse(exchange,chain,user,interfaceInfo);

    }

    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain,User user,InterfaceInfo interfaceInfo) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        // 缓存数据的工厂
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        // 拿到响应码
        HttpStatus statusCode = originalResponse.getStatusCode();
        if (statusCode == HttpStatus.OK) {
            // 装饰，增强能力
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                // 等调用完转发的接口后才会执行
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        // 往返回值里写数据
                        return super.writeWith(
                                fluxBody.map(dataBuffer -> {
                                    // 扣除积分
                                        boolean invoke = interfaceInfoServiceInner.invoke(interfaceInfo.getId(), user.getId(), interfaceInfo.getReduceScore());
                                        if (!invoke) {
                                            throw new BusinessException(ErrorCode.OPERATION_ERROR, "接口调用失败");
                                        }
                                    byte[] content = new byte[dataBuffer.readableByteCount()];
                                    dataBuffer.read(content);
                                    // 释放掉内存
                                    DataBufferUtils.release(dataBuffer);
                                    String data = new String(content, StandardCharsets.UTF_8);
                                    // 打印日志
                                    log.info("响应结果：" + data);
                                    return bufferFactory.wrap(content);
                                }));
                    } else {
                        // 8. 调用失败，返回一个规范的错误码
                        log.error("<--- {} 响应code异常", getStatusCode());
                    }
                    return super.writeWith(body);
                }
            };
            // 设置 response 对象为装饰过的
            return chain.filter(exchange.mutate().response(decoratedResponse).build());
        }
        // 降级处理返回数据
        return chain.filter(exchange);
    }


    /**
     * 返回异常情况
     * @param response
     * @param httpStatus
     * @return
     */
    private static Mono<Void> exceptionReturned(ServerHttpResponse response,HttpStatus httpStatus) {
        response.setStatusCode(httpStatus);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序，值越小，优先级越高
        return Ordered.HIGHEST_PRECEDENCE;
    }
}